/* Copyright (C) 2007-2008  klevgert  http://code.google.com/p/db4o-netbeans/

This file is part of the db4o-netbeans module suite.

db4o-netbeans is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o-netbeans is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

package com.db4o.nb.editor.nodes;

import com.db4o.ObjectContainer;
import com.db4o.ext.Db4oException;
import com.db4o.nb.editor.dialogs.SelectTypeDialog;
import com.db4o.nb.util.ClassLoaderUtil;
import com.db4o.nb.util.Db4oReflectorUtil;
import com.db4o.nb.util.DialogUtil;
import com.db4o.reflect.ReflectClass;
import com.db4o.reflect.ReflectField;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import org.openide.ErrorManager;
import org.openide.actions.PropertiesAction;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Index;
import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.actions.SystemAction;

/**
 * Representation of an object field.
 *
 * @author klevgert
 */
public class FieldNode extends AbstractNode {
  
  private static final String ICON_PATH = "com/db4o/nb/editor/resources/field.gif";
  
  // represented db4o field
  private ReflectField field;
  
  // db4o object containing the represented field
  private Object object; 
  
  // db4o database 
  private ObjectContainer objectContainer;
  
  /**
   * Constructs a node for a db4o field
   * @param field    field to be represented
   * @param object   object that owns the represented field
   * @param oc       db4o database housing of the object
   */
  public FieldNode(ReflectField field, Object obj, ObjectContainer oc) {
    super(new FieldNodeChildren(field, obj));
  
    this.field = field;
    this.object = obj;
    this.objectContainer = oc;
    
    // Get value of field to display it 
    Object fieldValue = getFieldValue();
    
    this.setName(field.getName() );
    this.setDisplayName( field.getName() + ": " + ((fieldValue!=null)? fieldValue.toString() : "null!") );    
    this.setIconBaseWithExtension(ICON_PATH);
  }

  /**
   * Gets the value of the represented field.
   * @return fields value
   */
  public Object getFieldValue() {

    this.field.setAccessible();
    return this.field.get(this.object);
  }
  
  /**
   * Sets the value of the represented field.
   * @param value    value to set into field.
   */
  public void setFieldValue(Object value) {
        
    try {
      // Update value
      Object realValue = ( value instanceof String )? 
                      Db4oReflectorUtil.getTypeSafeFieldValue((String)value, this.field) :
                      value;

      this.field.setAccessible();
      this.field.set(this.object, realValue);
      this.objectContainer.set(this.object);
     
      // Get field value and update the displayed text
      Object fieldValue = this.getFieldValue();
      this.setDisplayName( field.getName() + ": " + ((fieldValue!=null)? fieldValue.toString() : "null!") );    
      
    } catch (Exception ex) {
      ex.printStackTrace();
    }  
  }
  
  /**
   * Gets the action bound to the node.
   * @param popup    menu holding the actions
   */
  public Action[] getActions(boolean popup) {
    
    return new Action[] {
      new SetFieldAction(this),
      ((this.field.getFieldType().isCollection())? new AddObjectAction(this) : null),
      SystemAction.get(PropertiesAction.class)      
    };
  }  
  
  /**
   * Creates the property sheet for the node.
   * @return property sheet.
   */
  protected Sheet createSheet() {
    
    Sheet sheet = Sheet.createDefault();
    Sheet.Set set = sheet.createPropertiesSet();
    try {
      
      Property valueProp = createNameProperty();
      set.put (valueProp);
      Property nameProp = createValueProperty();
      set.put (nameProp);
        
    } catch (ClassNotFoundException ex) {
      
      String message = NbBundle.getMessage(this.getClass(), "ClassNotFound", ((ex != null)? ex.toString() : "") );
      DialogUtil.showErrorMessage(message);
      ErrorManager.getDefault().log(message);
        
    } catch (Exception ex) {
        ErrorManager.getDefault().notify (ex);
    }
    sheet.put(set);
    return sheet;
  }

  /**
   * Creates a value property.
   * @return value property.
   */
  private Property createValueProperty() throws ClassNotFoundException {
      
    String displayName = NbBundle.getMessage(this.getClass(), "Value" );
   
    Class clazz = findClass();
    Property valueProp = new PropertySupport.ReadWrite("value", clazz, 
            displayName, null) {
      public Object getValue() {
        return FieldNode.this.getFieldValue();
      }
      public void setValue(Object value) {
        FieldNode.this.setFieldValue(value);
      }
    };
    valueProp.setName("value");
    return valueProp;
  }

  /**
   * Creates a name property.
   * @return name property.
   */
  private Property createNameProperty() {

    String displayName = NbBundle.getMessage(this.getClass(), "Name" );
    Property nameProp = new PropertySupport.ReadOnly("name", String.class, 
            displayName, null) {
      public Object getValue() {
        return FieldNode.this.field.getName();
      }
    };
    nameProp.setName("name");
    return nameProp;
  }

  /**
   * Return a class for the field type.
   * @return class type.
   * @exception ClassNotFoundException
   */
  private Class findClass() throws ClassNotFoundException {
       
    ReflectClass rc = this.field.getFieldType();
    String name = rc.getName();

    Class clazz = null;
    if( !rc.isPrimitive() ) {
      Object fieldValue = this.getFieldValue();
      if( fieldValue==null ) {
        clazz = ClassLoaderUtil.getClassLoader().loadClass(name);
      } else {
        clazz = fieldValue.getClass();
      }
    } else {
      if( name.equals("int")) {
        clazz = int.class;
      } else if( name.equals("long")) {
        clazz = long.class;
      } else if( name.equals("double")) {
        clazz = double.class;
      } else if( name.equals("float")) {
        clazz = float.class;
      }
    }
    return clazz;
  }
  

  /**
   * Get the field value.
   * @param  rc     reflected class
   * @param  name   name of field to find
   * @return value of field.
   */
  public Object getInternalFieldValue(ReflectClass rc, String name) {

    ReflectField rf = rc.getDeclaredField(name);
    if( rf==null ) {
      String message = "Field not found.\n";
      ErrorManager.getDefault().log(message);
      return null;
    }
    Object fieldValue = rf.get(this.object);
    return fieldValue;
  }
 
  // <editor-fold defaultstate="collapsed" desc=" Actions ">                          

  /**
   * Action that adds a new object.
   */
  public class SetFieldAction extends AbstractAction {
    
    private FieldNode node;
    
    public SetFieldAction(FieldNode node) {
      
      String displayName = NbBundle.getMessage(this.getClass(), "ResetField");
      this.putValue(NAME, displayName);
      this.node = node;
    }
    
    public void actionPerformed(ActionEvent e) {

      // Request confirmation from user
      String message = NbBundle.getMessage(this.getClass(), "ConfirmResetField");
      if (DialogUtil.confirmYesNo(message).equals(DialogUtil.YES)) {

        Object obj = this.node.object;
        ReflectClass rc = this.node.field.getFieldType();

        Object newInstance = null;
        if( rc.isCollection() ) {

          newInstance = new ArrayList();
 
        } else {
          newInstance = rc.newInstance();
        }
        this.node.setFieldValue( newInstance );
        this.node.setChildren( new FieldNodeChildren(this.node.field, obj) );
      }
    }
  } 

  /**
   * Action to add objects to the database. 
   */
  private class AddObjectAction extends AbstractAction {
    
    private FieldNode node;
    
    public AddObjectAction(FieldNode node) {
      String name = NbBundle.getMessage(this.getClass(), "AddObject");
      this.putValue(NAME,name);
      
      this.node = node;
    }
    
    public void actionPerformed(ActionEvent e) {
      
      if( !this.node.field.getFieldType().isCollection() ) {
        return;
      }
      
      SelectTypeDialog std = new SelectTypeDialog(new JFrame(),true);
      std.setObjectContainer(FieldNode.this.objectContainer);
      std.setVisible(true);

      if( std.getReturnStatus() == SelectTypeDialog.RET_OK) {

        String type = std.getSelectedClass();
        if( type.length() > 0 ) {
          
          try {

            Object inst = ClassLoaderUtil.newInstance(type, this.node.objectContainer);
            Collection coll = (Collection)this.node.getFieldValue();
            coll.add(inst);
            this.node.objectContainer.set(coll);
            
            this.node.setChildren( new FieldNodeChildren(this.node.field, this.node.object) );

          } catch (Exception ex) {
            
            String errmsg = NbBundle.getMessage(this.getClass(), "CannotCreateObject", ((ex!=null)? ex.toString(): "") );
            DialogUtil.showErrorMessage(errmsg);
          }
        }
      }
      
 //     DialogDescriptor dd = new DialogDescriptor( std , title);
      
    }
  }
  
  // </editor-fold>                          
   
  // <editor-fold defaultstate="collapsed" desc=" Children ">                          

  /**
   * Holder for the children of the field node.
   *
   * @author klevgert
   */
  public static class FieldNodeChildren extends Index.ArrayChildren {

    private ReflectField field;
    private Object object;

    /** 
     * Creates a new instance of FieldChildren 
     * @param field    represented field
     * @param obj      object instance that owns represented field.
     */
    public FieldNodeChildren(ReflectField field, Object obj) {
      this.field = field;
      this.object = obj; 
    }

    /**
     * Initializes the children.
     * @return collection of sub nodes. 
     */
    protected Collection initCollection() {

      ArrayList children = new ArrayList();
      Object fieldValue = null;

      try {

        fieldValue = this.field.get(this.object);

      } catch(Exception e) {
        ErrorManager.getDefault().log(ErrorManager.WARNING, e.getMessage());
      }
      
      try {
        
        if( fieldValue == null ) {
            return children;
        }
        ReflectClass rc = this.field.getFieldType();

        Node node = null;

        // expand non-primitive types
        if( rc!=null && !rc.isPrimitive() ) {

          ObjectNode objNode = (ObjectNode)this.getNode().getParentNode();
          ObjectContainer oc = objNode.getObjectContainer();
          oc.activate(fieldValue, 2);

          // collection?
          if( rc.isCollection() ) {
            Collection col = (Collection)fieldValue;
            node = new CollectionNode(col, oc);
            node.setDisplayName(col.getClass().getName() + " #"+ col.size());

            Iterator iter = col.iterator();
            while( iter.hasNext() ) {
              long oid = oc.ext().getID(iter.next());
              node = new ObjectNode(oid, oc);
              children.add(node);
            }

          } else {

            long oid = oc.ext().getID(fieldValue);
            if( oid > 0 ) {
              node = new ObjectNode(oid,oc);
              children.add(node);
            }
          }

        }		
        
      } catch(Db4oException e) {
        
        DialogUtil.showErrorMessage(
            "Error accessing content:\n" + ((e!=null)? e.toString(): "") + 
            "\n\nCheck database connection state.");
        ErrorManager.getDefault().log(ErrorManager.ERROR, (e!=null)?e.toString():"");
        
      }		
      return children;
    }

  }
 
  // </editor-fold>                          

}
